/**
 * Socket缓存循环队列构造函数
 * 这个类只能解析定长头部的协议和协议内容不应该很长,如传输视频.
 * 缓存会将所有准备好指定长度的字节内容放入内存中 然后handleData的执行
 * 类似http这样的就是头部非定长的
 * @param size     缓存的初始大小
 * @param headSize 自定义协议的head字节长度
 * @constructor
 */
function SocketDataManage(size = 512, headSize = 2) {
    //初始化一个循环队列,维护队头和队尾
    this.buf = Buffer.alloc(size);
    //数据读取首下标
    this.head = 0;
    //数据插入首下标
    this.tail = 0;
    this.size = size;
    //用来保存下一个报文的总长度.一般是从头部获取
    this.nextSize = 0;
    this.headSize = headSize;
    this.readable = 0;
}

/**
 * 计算可读长度
 * @returns {number} 可读长度
 */
SocketDataManage.prototype.readableLength = function () {
    return this.readable;
}
/**
 * 计算剩余空间
 * @returns {number}
 */
SocketDataManage.prototype.remain = function () {
    return this.size - this.readableLength();
};
/**
 * 读取数据 会修改 head
 * @param length
 * @param claer
 * @returns {Buffer}
 */
SocketDataManage.prototype.read = function (length, clear=true) {
    if (length > this.readableLength()) {
        throw new Error('length index out');
    }

    let endIndex = this.head + length;

    let data = null;
    if (endIndex > this.size) {
        data = Buffer.concat([this.buf.subarray(this.head, this.size), this.buf], length);
    } else {
        data = this.buf.subarray(this.head, endIndex);
    }
    if (clear) {
        this.head = endIndex >= this.size ? endIndex % this.size : endIndex;
        this.readable = this.readable - length;
    }
    return data;
};
/**
 * 重新开辟一个缓存空间,并初始化
 * @param minSize
 */
SocketDataManage.prototype.resize = function (minSize) {
    //两倍两倍的增加
    let reSize = this.size << 1;
    while (reSize < minSize) {
        reSize = reSize << 1;
    }
    this.size = reSize;
    let buffer = Buffer.alloc(reSize);
    let canRead = this.readableLength();
    this.buf.copy(buffer, 0, this.head, this.tail);
    this.buf = buffer;
    this.head = 0;
    this.tail = canRead;
};
/**
 * 根据自己定义的协议解析
 * @param data 字节数组. 长度是构造函数中的headSize
 */
SocketDataManage.prototype.getNextSize = function (data) {
    throw Error('abstract method');
};
/**
 * 当缓存 准备好一个getNetSize大小的数据就会自动调用
 * 解决了沾包和拆包的问题
 * 只要发送的数据是按照自己的协议发送的
 * 那么这里就是每次请求的完整数据
 * @param data
 */
SocketDataManage.prototype.handleData = function (data) {
    throw Error('abstract method');
};
/**
 * 复制数据到this.buf的剩余空间中 如果剩余空间不足则会调用 reSize方法
 * @param data
 */
SocketDataManage.prototype.copyDataIntoBuf = function (data) {
    if (data.length > this.remain()) {
        this.resize(this.readableLength() + data.length);
    }
    let endIndex = data.length + this.tail;
    //当数据存放起点和终点跨越了循环队列的物理首地址
    if (endIndex > this.size) {
        let end = this.size - this.tail;
        //先将数据填充到物理队尾
        this.copyDataIntoBuf(data.subarray(0, end));
        //再将剩余数据复制到空闲空间
        this.copyDataIntoBuf(data.subarray(end, data.length));
        return;
    }
    //此方法只适用于顺序表.所以遇到跨越循环队列的时候需要分开复制
    data.copy(this.buf, this.tail, 0, data.length);
    this.tail = endIndex >= this.size ? endIndex % this.size : endIndex;
    this.readable = this.readable + data.length;
};
SocketDataManage.prototype.putData = function (data) {
    if (data) {
        this.copyDataIntoBuf(data);
    }
    if (!this.nextSize && this.readableLength() >= this.headSize) {
        this.nextSize = this.getNextSize(this.read(this.headSize, false));
    }
    if (this.nextSize !== 0 && this.readableLength() >= this.nextSize) {
        try {
            let data = this.read(this.nextSize);
            this.handleData(data)
        } finally {
            this.nextSize = 0
        }
        this.putData(null);

    }
}
module.exports = SocketDataManage;